#include "board.hpp"
#include "chess_piece.hpp"
#include <memory>
#include <vector>
#include <algorithm>
#include <functional>
#include <iostream>
// #define DEBUG

using PiecePtr = std::shared_ptr<ChessPiece>;

Board::Board(Camp camp): _board(9), _side(camp) {
    for(int x = 0 ; x < 9 ; x++) {
        _board[x].resize(10);
    }
}

Board::Board(const Board &board): _board(9), _side(board._side), _undo_buffer(board._undo_buffer) {
    for(int x = 0 ; x < 9 ; x++) {
        _board[x].resize(10);
    }
    for(int x = 0 ; x < 9 ; x++) {
        for(int y = 0 ; y < 10 ; y++) {
            if(board._board[x][y]) {
                place(board._board[x][y]->clone(), Location(x, y));
            }
        }
    }
}

Board::Board(const Board &&board): 
    _board(std::move(board._board)),
    _red(std::move(board._red)),
    _black(std::move(board._black)),
    _side(std::move(board._side)),
    _undo_buffer(board._undo_buffer) { }

Board& Board::operator=(const Board &board) {
    if(this == &board) return *this;
    _red.clear();
    _black.clear();
    _undo_buffer = board._undo_buffer;
    for(int x = 0 ; x < 9 ; x++) {
        for(int y = 0 ; y < 10 ; y++) {
            if(_board[x][y]) _board[x][y].reset();
            if(board._board[x][y]) {
                place(board._board[x][y]->clone(), Location(x, y));
            }
        }
    }
    return *this;
}

Board& Board::operator=(const Board &&board) {
    if(this == &board) return *this;
    _board = std::move(board._board);
    _red = std::move(board._red);
    _black = std::move(board._black);
    _side = std::move(board._side);
    _undo_buffer = board._undo_buffer;
    return *this;
}

Board::~Board() { }

void Board::init() {
    Camp _other_side = _side == Camp::RED ? Camp::BLACK : Camp::RED;
    place(std::make_shared<Rock>(_side), Location(0, 0));
    place(std::make_shared<Knight>(_side), Location(1, 0));
    place(std::make_shared<Bishop>(_side), Location(2, 0));
    place(std::make_shared<Guard>(_side), Location(3, 0));
    place(std::make_shared<King>(_side), Location(4, 0));
    place(std::make_shared<Guard>(_side), Location(5, 0));
    place(std::make_shared<Bishop>(_side), Location(6, 0));
    place(std::make_shared<Knight>(_side), Location(7, 0));
    place(std::make_shared<Rock>(_side), Location(8, 0));
    place(std::make_shared<Cannon>(_side), Location(1, 2));
    place(std::make_shared<Cannon>(_side), Location(7, 2));
    place(std::make_shared<Pawn>(_side), Location(0, 3));
    place(std::make_shared<Pawn>(_side), Location(2, 3));
    place(std::make_shared<Pawn>(_side), Location(4, 3));
    place(std::make_shared<Pawn>(_side), Location(6, 3));
    place(std::make_shared<Pawn>(_side), Location(8, 3));
    place(std::make_shared<Rock>(_other_side), Location(0, 9));
    place(std::make_shared<Knight>(_other_side), Location(1, 9));
    place(std::make_shared<Bishop>(_other_side), Location(2, 9));
    place(std::make_shared<Guard>(_other_side), Location(3, 9));
    place(std::make_shared<King>(_other_side), Location(4, 9));
    place(std::make_shared<Guard>(_other_side), Location(5, 9));
    place(std::make_shared<Bishop>(_other_side), Location(6, 9));
    place(std::make_shared<Knight>(_other_side), Location(7, 9));
    place(std::make_shared<Rock>(_other_side), Location(8, 9));
    place(std::make_shared<Cannon>(_other_side), Location(1, 7));
    place(std::make_shared<Cannon>(_other_side), Location(7, 7));
    place(std::make_shared<Pawn>(_other_side), Location(0, 6));
    place(std::make_shared<Pawn>(_other_side), Location(2, 6));
    place(std::make_shared<Pawn>(_other_side), Location(4, 6));
    place(std::make_shared<Pawn>(_other_side), Location(6, 6));
    place(std::make_shared<Pawn>(_other_side), Location(8, 6));
}

void Board::place(const PiecePtr piece, const Location loc) {
    _board[loc.x][loc.y] = piece;
    if(piece->camp() == Camp::RED)
        _red[piece] = Location(loc.x, loc.y);
    else
        _black[piece] = Location(loc.x, loc.y);
}

void Board::remove(Location loc) {
    if(_board[loc.x][loc.y]->camp() == Camp::RED) {
        auto it = _red.find(_board[loc.x][loc.y]);
        if(it != _red.end())
            _red.erase(it);
        else
            throw "Remove empty place!";
    } else {
        auto it = _black.find(_board[loc.x][loc.y]);
        if(it != _black.end())
            _black.erase(it);
        else
            throw "Remove empty place!";
    }
    _board[loc.x][loc.y].reset();
}

void Board::rotate() {
    for(const auto &piece: _red) _board[piece.second.x][piece.second.y].reset();
    for(const auto &piece: _black) _board[piece.second.x][piece.second.y].reset();
    for(auto &piece: _red) {
        piece.second.rotate();
        _board[piece.second.x][piece.second.y] = piece.first;
    }
    for(auto &piece: _black) {
        piece.second.rotate();
        _board[piece.second.x][piece.second.y] = piece.first;
    }
    for(auto &state: _undo_buffer) {
        state.first.rotate();
    }
    _side = _side == Camp::RED ? Camp::BLACK : Camp::RED;
}


Camp Board::side() const {
    return _side;
}

const PiecePtr& Board::at(const Location loc) const {
    return _board[loc.x][loc.y];
}

int Board::count(const Camp camp) const {
    if(camp == Camp::RED)
        return _red.size();
    else
        return _black.size();
}

void Board::move(const Move &m) {
    Location from = m.from();
    Location to = m.to();
    #ifdef DEBUG
    if(!_board[from.x][from.y]) 
        throw "Move from empty place!";
    if(!_board[from.x][from.y]->is_available(*this, m)) 
        throw "Invalid move!";
    #endif
    _undo_buffer.push_back(std::make_pair(m, _board[to.x][to.y]));
    if(_board[to.x][to.y]) 
        remove(Location(to.x, to.y));
    _board[to.x][to.y] = _board[from.x][from.y];
    _board[from.x][from.y].reset();
    if(_board[to.x][to.y]->camp() == Camp::RED)
        _red[_board[to.x][to.y]] = to;
    else
        _black[_board[to.x][to.y]] = to;
}

void Board::undo() {
    #ifdef DEBUG
    if(_undo_buffer.empty()) throw "Undo failed!";
    #endif
    auto state = _undo_buffer.back();
    _undo_buffer.pop_back();
    Location from = state.first.from();
    Location to = state.first.to();
    _board[from.x][from.y] = _board[to.x][to.y];
    _board[to.x][to.y] = state.second;
    if(_board[to.x][to.y]) {
        if(_board[to.x][to.y]->camp() == Camp::RED)
            _red[_board[to.x][to.y]] = to;
        else
            _black[_board[to.x][to.y]] = to;
    }
    if(_board[from.x][from.y]->camp() == Camp::RED)
        _red[_board[from.x][from.y]] = from;
    else
        _black[_board[from.x][from.y]] = from;
}

std::vector<Move> Board::available_move() const {
    std::vector<Move> res;
    const auto &pieces = _side == Camp::RED ? _red : _black;
    for(auto piece: pieces) {
        auto available_move = piece.first->available_move(*this, piece.second);
        for(auto move: available_move) res.push_back(move);
    }
    return res;
}

bool Board::judge(Camp &winner) const {
    auto red_king = std::find_if(_red.begin(), _red.end(),
        [](const auto &piece) -> bool {
            return piece.first->type() == ChessPiece::Type::KING;
        });
    auto black_king = std::find_if(_black.begin(), _black.end(),
        [](const auto &piece) -> bool {
            return piece.first->type() == ChessPiece::Type::KING;
        });
    if(red_king == _red.end()) {
        winner = Camp::BLACK;
        return true;
    }
    if(black_king == _black.end()) {
        winner = Camp::RED;
        return true;
    }
    return false;
}

void Board::print() const {
    static int count=0;
    std::cout << count++ << std::endl;
    for(int y = 9 ; y >= 0 ; y--) {
        for(int x = 0 ; x < 9 ; x++) {
            if(_board[x][y]) {
                if(_board[x][y]->camp() == Camp::RED)
                    std::cout << "\033[31;47m";
                else
                    std::cout << "\033[30;47m";
                switch (_board[x][y]->type()) {
                    case ChessPiece::Type::KING:   std::cout << 'K';break;
                    case ChessPiece::Type::ROCK:   std::cout << 'R';break;
                    case ChessPiece::Type::CANNON: std::cout << 'C';break;
                    case ChessPiece::Type::KNIGHT: std::cout << 'H';break;
                    case ChessPiece::Type::GUARD:  std::cout << 'G';break;
                    case ChessPiece::Type::BISHOP: std::cout << 'B';break;
                    case ChessPiece::Type::PAWN:   std::cout << 'P';break;
                }
                std::cout << "\033[0m";
            } else {
                std::cout << "\033[47m \033[0m";
            }
        }
        std::cout << std::endl;
    }
    std::cout << std::endl;
}

void Board::_data_check() const {
    bool check = true;
    for(int x = 0 ; x < 9 ; x++) {
        for(int y = 0 ; y < 10 ; y++) {
            if(_board[x][y]) {
                if(_board[x][y]->camp() == Camp::RED) {
                    if(_red.find(_board[x][y]) == _red.end())
                        check = false;
                } else {
                    if(_black.find(_board[x][y]) == _black.end())
                        check = false;
                }
            }
        }
    }
    for(const auto &piece: _red) {
        if(!_board[piece.second.x][piece.second.y]
            || _board[piece.second.x][piece.second.y] != piece.first)
            check = false;
    }
    for(const auto &piece: _black) {
        if(!_board[piece.second.x][piece.second.y]
            || _board[piece.second.x][piece.second.y] != piece.first)
            check = false;
    }
    if(!check) throw "Check failed!";
}